﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AMatysiak.Methods;

namespace AMatysiak.Converters
{
    public class ChangeAmountToWords
    {
        public static string space = "  ";

        private readonly string[] Units =
        {
            "Zero", "Jeden", "Dwa", "Trzy", "Cztery", "Pięć", "Sześć",
            "Siedem", "Osiem", "Dziewięć", "Dziesięć", "Jedenaście",
            "Dwanaście", "Trzynaście", "Czternaście", "Piętnaście",
            "Szesnaście", "Siedemnaście", "Osiemnaście", "Dziewiętnaście"
        };

        private readonly string[] Tens =
        {
            "Dwadzieścia", "Trzydzieści", "Czterdzieści", "Pięćdziesiąt",
            "Sześćdziesiąt", "Siedemdziesiąt", "Osiemdziesiąt",
            "Dziewięćdziesiąt"
        };

        private readonly string[] Hundreds =
        {
            "", "Sto", "Dwieście", "Trzysta", "Czterysta", "Pięćset",
            "Sześćset", "Siedemset", "Osiemset", "Dziewięćset"
        };

        private readonly string[,] OtherUnits =
        {
            { "Tysiąc", "Tysiące", "Tysięcy" },
            { "Milion", "Miliony", "Milionów" },
            { "Miliard", "Miliardy", "Miliardów" }
        };

        private string amount;
        private long complex = 0;
        private int rest = 0;
        private string before = "";
        private string after = "";
        private StringBuilder words;
        private StringBuilder valueInWords;
        private StringBuilder smallValueInWords;

        public static string ToWords(double amount)
        {
            return ToWords(amount.ToString());
        }

        public static string ToWords(long amount)
        {
            return ToWords(amount.ToString());
        }

        public static string ToWords(string amount)
        {
            ChangeAmountToWords words = new ChangeAmountToWords(amount);
            return words.ToWords();
        }

        private ChangeAmountToWords(string amount)
        {
            this.amount = amount;
            words = new StringBuilder();
        }

        public string ToWords()
        {
            castToNumbers();

            pronounceAmount();

            proponounceZloty();

            pronounceGrosze();

            return words.ToString();
        }

        private void castToNumbers()
        {
            amount = amount.NormalizeCurrencyValue();
            if (amount.Length == 0)
                return;
            if (!amount.Contains(','))
            {
                before = amount;
                complex = long.Parse(amount);
                return;
            }

            before = amount.SubstringBefore(",");
            complex = long.Parse(before);

            after = amount.SubstringAfter(",");
            rest = int.Parse(after);
        }

        private void pronounceAmount()
        {
            string amount = amountToWords();
            words.Append(amount);
        }

        private void proponounceZloty()
        {
            string zl = space;
            if (has1AtEnd())
                zl += "Złoty";
            else if (isFrom2To4())
                zl += "Złote";
            else
                zl += "Złotych";

            words.Append(zl);
        }

        private bool has1AtEnd()
        {
            return before.Length > 0 && before[before.Length - 1] == '1' && isNotTeen();
        }

        private bool isNotTeen()
        {
            return !(before.Length >= 2 && before[before.Length - 2] == '1');
        }

        private bool isFrom2To4()
        {
            return before.Length > 0 && before[before.Length - 1] > '1' && before[before.Length - 1] < '5' && isNotTeen();
        }

        private void pronounceGrosze()
        {
            string gr = "";
            if (after.Length == 1)
                gr = "0";
            else if (after.Length == 0)
                gr = "00";

            gr = "{0}{1}{2}/100".FormatWith(space, gr, after);
            words.Append(gr);
        }

        private string amountToWords()
        {
            if (complex == 0)
                return Units[0];

            return transformToWords();
        }

        private string transformToWords()
        {
            valueInWords = new StringBuilder();
            int smallValue = (int)numberToWords(valueInWords, complex, 0);
            if (smallValue > 0)
                appendSmallValue(valueInWords, smallValue);
            return valueInWords.ToString();
        }

        private void appendSmallValue(StringBuilder valueInWords, int smallValue)
        {
            if (valueInWords.Length > 0)
                valueInWords.Append(space);

            valueInWords.Append(SmallValueToWords(smallValue));
        }

        private long numberToWords(StringBuilder valueInWords, long n, int level)
        {
            int smallValue = 0;
            long divisor = (long)Math.Pow(1000, level + 1);

            if (divisor <= n)
            {
                n = numberToWords(valueInWords, n, level + 1);
                smallValue = (int)(n / divisor);

                appendBigValue(level, smallValue);
            }

            return n - smallValue * divisor;
        }

        private void appendBigValue(int level, int smallValue)
        {
            if (valueInWords.Length > 0)
                valueInWords.Append(space);

            if (smallValue > 1)
            {
                valueInWords.Append(SmallValueToWords(smallValue));
                valueInWords.Append(space);
            }

            valueInWords.Append(OtherUnits[level, getBigUnitIndex(smallValue)]);
        }

        private int getBigUnitIndex(long n)
        {
            int lastDigit = (int)n % 10;

            if (isUp5(n, lastDigit))
                return 2;
            if (isNotAUnit(lastDigit))
                return 1;
            return 0;
        }

        private static bool isUp5(long n, int lastDigit)
        {
            return (n >= 10 && (n <= 20 || lastDigit == 0)) || (lastDigit > 4);
        }

        private static bool isNotAUnit(int lastDigit)
        {
            return lastDigit != 1;
        }

        private string SmallValueToWords(int n)
        {
            if (n == 0)
                return String.Empty;

            smallValueInWords = new StringBuilder();

            n = convertHundreds(n);

            convertTensAndUnits(n);

            return smallValueInWords.ToString();
        }

        private int convertHundreds(int n)
        {
            int temp = n / 100;
            if (temp > 0)
            {
                smallValueInWords.Append(Hundreds[temp]);
                n -= temp * 100;
            }
            return n;
        }

        private void convertTensAndUnits(int n)
        {
            if (n > 0)
            {
                if (smallValueInWords.Length > 0)
                    smallValueInWords.Append(space);

                if (isUnitOrTeen(n))
                    smallValueInWords.Append(Units[n]);
                else
                    combineTensWithUnit(n);
            }
        }

        private static bool isUnitOrTeen(int n)
        {
            return n < 20;
        }

        private void combineTensWithUnit(int n)
        {
            smallValueInWords.Append(Tens[(n / 10) - 2]);
            int lastDigit = n % 10;

            if (lastDigit > 0)
            {
                smallValueInWords.Append(space);
                smallValueInWords.Append(Units[lastDigit]);
            }
        }
    }
}
